package com.gzx.plugin.website.access.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.gzx.plugin.website.access.bo.GzxAccessLogAddBo;
import com.gzx.plugin.website.access.bo.GzxAccessLogPageBo;
import com.gzx.plugin.website.access.entity.GzxAccessLog;
import com.gzx.plugin.website.access.enums.SmsVcSign;
import com.gzx.plugin.website.access.mapper.GzxAccessLogMapper;
import com.gzx.plugin.website.access.service.GzxAccessLogService;
import com.gzx.plugin.website.access.vo.GzxAccessLogVo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.common.cache.CommonCacheOperator;
import vip.xiaonuo.common.consts.IntConstant;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.util.*;

import java.util.*;
import java.util.stream.Collectors;

import static org.apache.commons.lang.StringEscapeUtils.escapeSql;

/**
 * @author zjt
 * @description
 * @date 2025/4/17 16:31
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GzxAccessLogServiceImpl implements GzxAccessLogService {

    private final GzxAccessLogMapper accessLogMapper;

    private final CommonCacheOperator cacheOperator;

    private final Random random = new Random();

    @Value("${sms.url}")
    private String url;

    @Value("${sms.mch-id}")
    private String mchId;

    @Value("${sms.app-id}")
    private String appId;

    @Value("${sms.app-key}")
    private String appKey;

    /**
     * 发送短信验证码
     *
     * @param phone
     */
    @Override
    public Integer smsVc(String phone, HttpServletRequest httpServletRequest) {
        checkLimit(phone, CommonIpAddressUtil.getIp(httpServletRequest));
        String serverName = httpServletRequest.getHeader("hostName");
        SmsVcSign smsVcSign = SmsVcSign.getSignByHost(serverName);
        int randomNum = 100000 + random.nextInt(900000);
        Map<String, Object> reqBody = new HashMap<>();
        reqBody.put("MchId", mchId);
        reqBody.put("AppId", appId);
        reqBody.put("Version", "1.2.0");
        reqBody.put("Type", "1");
        reqBody.put("SessionContext", "【" + smsVcSign.getSign() + "】本次登录验证码为" + randomNum + "，请在5分钟内完成验证，请勿将验证码泄露给他人");
        reqBody.put("PhoneNumberSet", List.of(phone));
        long timeStamp = System.currentTimeMillis();
        reqBody.put("TimeStamp", timeStamp);
        reqBody.put("SignType", "MD5");
        String sb = "AppId=" +
                appId +
                "&MchId=" +
                mchId +
                "&SignType=MD5" +
                "&TimeStamp=" +
                timeStamp +
                "&Type=1&Version=1.2.0&key=" +
                appKey;
        reqBody.put("Signature", CommonCryptogramUtil.doMd5(sb).toUpperCase());
        String respBody = CommonHttpUtil.post(url, null, JSON.toJSONString(reqBody), "官网短信验证码");
        log.info("调用联麓短信发送接口响应：{}", respBody);
        String status = JSONObject.parseObject(respBody).getString("status");
        if ("00".equals(status)) {
            cacheOperator.setCatchString(phone, String.valueOf(randomNum), IntConstant.VALUE_300);
            return randomNum;
        } else {
            throw new CommonException("短信发送异常");
        }
    }

    /**
     * 验证短信验证码
     *
     * @param accessLogAddBo
     * @param httpServletRequest
     */
    @Override
    @DS("website")
    @Transactional(rollbackFor = Exception.class)
    public String verifySmsVc(GzxAccessLogAddBo accessLogAddBo, HttpServletRequest httpServletRequest) {
        String redisSmsCode = cacheOperator.getCatchString(accessLogAddBo.getLiaisonPhone());
        if (StringUtils.isNotBlank(redisSmsCode) && redisSmsCode.equals(accessLogAddBo.getSmsCode())) {
            String ip = CommonIpAddressUtil.getIp(httpServletRequest);
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String token = CommonCryptogramUtil.doMd5(accessLogAddBo.getLiaisonPhone() + accessLogAddBo.getDebtorCardId() + ip + uuid);
            cacheOperator.setCatchString(token, uuid, 300);
            return token;
        }
        throw new CommonException("短信验证码错误");
    }

    /**
     * 新增访问记录
     *
     * @param ip
     * @param configId
     * @param phone
     * @param debtorCardId
     */
    @Override
    public void add(String ip, Long configId, String phone, String debtorCardId) {
        GzxAccessLog accessLog = new GzxAccessLog();
        accessLog.setLiaisonPhone(phone);
        accessLog.setDebtorCardId(debtorCardId);
        accessLog.setConfigId(configId);
        accessLog.setIp(ip);
        accessLog.setCreateTime(CommonDateUtil.getCurrentTimeStr());
        accessLogMapper.insert(accessLog);
    }

    /**
     * 查询访问记录
     *
     * @param accessLogPageBo
     */
    @Override
    @DS("website")
    public Page<GzxAccessLogVo> pageList(GzxAccessLogPageBo accessLogPageBo) {
        return accessLogMapper.selectVoPage(new Page<>(accessLogPageBo.getCurrent(), accessLogPageBo.getSize()),
                Wrappers.<GzxAccessLog>lambdaQuery()
                        .eq(accessLogPageBo.getConfigId() != null, GzxAccessLog::getConfigId, accessLogPageBo.getConfigId())
                        .eq(StringUtils.isNotBlank(accessLogPageBo.getDebtorCardId()), GzxAccessLog::getDebtorCardId, CommonCryptogramUtil.doSm1AesEncrypt(accessLogPageBo.getDebtorCardId()))
                        .eq(StringUtils.isNotBlank(accessLogPageBo.getLiaisonPhone()), GzxAccessLog::getLiaisonPhone, CommonCryptogramUtil.doSm1AesEncrypt(accessLogPageBo.getLiaisonPhone()))
                        .between(StringUtils.isNotBlank(accessLogPageBo.getBeginTime()) && StringUtils.isNotBlank(accessLogPageBo.getEndTime()), GzxAccessLog::getCreateTime, accessLogPageBo.getBeginTime(), accessLogPageBo.getEndTime())
                        .orderByDesc(GzxAccessLog::getId));
    }

    /**
     * 访问记录导出
     *
     * @param accessLogPageBo
     * @param httpServletResponse
     */
    @Override
    @DS("website")
    public void exportAccessLog(GzxAccessLogPageBo accessLogPageBo, HttpServletResponse httpServletResponse) {
        List<GzxAccessLogVo> accessLogVoList = accessLogMapper.selectVoList(Wrappers.<GzxAccessLog>lambdaQuery()
                .eq(StringUtils.isNotBlank(accessLogPageBo.getDebtorCardId()), GzxAccessLog::getDebtorCardId, CommonCryptogramUtil.doSm1AesEncrypt(accessLogPageBo.getDebtorCardId()))
                .eq(StringUtils.isNotBlank(accessLogPageBo.getLiaisonPhone()), GzxAccessLog::getLiaisonPhone, CommonCryptogramUtil.doSm1AesEncrypt(accessLogPageBo.getLiaisonPhone()))
                .between(StringUtils.isNotBlank(accessLogPageBo.getBeginTime()) && StringUtils.isNotBlank(accessLogPageBo.getEndTime()), GzxAccessLog::getCreateTime, accessLogPageBo.getBeginTime(), accessLogPageBo.getEndTime())
                .orderByDesc(GzxAccessLog::getId));
        if (CollectionUtil.isEmpty(accessLogVoList)) {
            throw new CommonException("暂无可导出的数据");
        }
        CommonExcelUtil.exportExcel("官网后台数据", accessLogVoList, GzxAccessLogVo.class, httpServletResponse);
    }

    /**
     * 同步访问记录
     *
     * @param accessLogList
     */
    @Override
    @DS("website")
    public void insertBatch(List<GzxAccessLog> accessLogList) {
        List<List<GzxAccessLog>> batchList = Lists.partition(accessLogList, IntConstant.VALUE_1000);
        for (List<GzxAccessLog> batch : batchList) {
            String sql = batch.stream()
                    .map(s -> String.format("('%s', '%s', '%s', %s, '%s','%s')",
                            escapeSql(s.getDebtorCardId()),
                            escapeSql(s.getLiaisonPhone()),
                            escapeSql(s.getChannel()),
                            escapeSql(String.valueOf(s.getConfigId())),
                            escapeSql(s.getIp()),
                            escapeSql(s.getCreateTime())
                    ))
                    .collect(Collectors.joining(","));
            accessLogMapper.insertBatchByValSQL(sql.replace("'null'", "null"));
        }
    }


    public boolean checkLimit(String phone, String ip) {
        // 手机号分钟级限流
        String minuteKey = "phone_min:" + phone;
        if (allowRequest(minuteKey, 60, 1)) {
            throw new CommonException("操作过于频繁，请1分钟后再试");
        }

        // 手机号天级限流
        String dayKey = "phone_day:" + phone;
        if (allowRequest(dayKey, 86400, 10)) {
            throw new CommonException("今日操作次数已达上限");
        }

        // IP天级限流
        String ipKey = "ip_day:" + ip;
        if (allowRequest(ipKey, 86400, 10)) {
            throw new CommonException("当前IP请求过多");
        }
        return true;
    }

    public boolean allowRequest(String key, int seconds, int maxCount) {
        String redisKey = "limit:" + key;
        Long count = cacheOperator.atomicIncrement(redisKey);
        if (count != null && count == 1) {
            cacheOperator.setExpireIfKeyExists(redisKey, seconds);
        }
        return count == null || count > maxCount;
    }
}
